/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#include "carina/settings-file.hh"
#include "carina/common/system.hh"
#include "carina/file-system.hh"
#include "carina/utils.hh"

#include "carina/carina-config-driver.hh"

#include <algorithm>

namespace Carina
{
SettingsFile::SettingsFile() {}

// TODO: that's stupid -- there should be a better system to define configuration values. Too much duplicate code to save and load stuff.
bool SettingsFile::load(const string& filename)
{
	Config::Driver config;
	if(!config.parse(filename))
		return false;
    
	auto root = config.getASTRoot();
	
	auto renderer = Config::FindChild(root, "renderer");
    m_RendererName = renderer ? Config::ExtractValue<string>(renderer) : string();
    
    m_LoggingLevel = CE_LOG_INFO;
	auto log = Config::FindChild(root, "log");
    if(log)
    {
        auto name_node = Config::FindChild(log, "name");
        if(name_node)
            m_LogFilename = Config::ExtractValue<string>(name_node);

		auto level = Config::FindChild(log, "level");
        if(level)
        {
			auto log_level_name = Config::ExtractValue<string>(level);
            if(log_level_name == "INFO")
                m_LoggingLevel = CE_LOG_INFO;
            else if(log_level_name == "DEBUG")
                m_LoggingLevel = CE_LOG_DEBUG;
            else if(log_level_name == "WARNING")
                m_LoggingLevel = CE_LOG_WARNING;
            else if(log_level_name == "ERROR")
                m_LoggingLevel = CE_LOG_ERROR;
            else if(log_level_name == "FATAL ERROR")
                m_LoggingLevel = CE_LOG_FATAL;
            else
                CE_ASSERT(false, "Unknown logging level");
        }
    }

    string resname;
    auto resources = Config::FindChild(root, "resources");
    if(resources)
	{
		auto resource_list = Config::ExtractValue<const AST::List*>(resources);
		for(auto i = resource_list->current(); i != resource_list->end(); ++i)
			this->addResource(Config::ExtractValue<string>(i->extract<Config::Entry>()));
	}

	auto ctx_main = Config::FindChild(root, "contexts");
    if(ctx_main)
	{
		auto ctx_list = Config::ExtractValue<const AST::List*>(ctx_main);
        for(auto i = ctx_list->current(); i != ctx_list->end(); ++i)
        {
            m_Contexts.push_back(ContextSettings());
            ContextSettings& ctx_st = m_Contexts.back();
			ctx_st._load(Config::ExtractValue<const AST::List*>(i->extract<Config::Entry>()));
        }
	}
    return true;
}

bool SettingsFile::save(const string& filename) const
{
	AST::NodeT<AST::List> root_front, *root;

	root = Config::InsertEntry(&root_front, "renderer", m_RendererName);

    string log_level_name;
    switch(m_LoggingLevel)
    {
    case CE_LOG_INFO: log_level_name = "INFO"; break;
    case CE_LOG_DEBUG: log_level_name = "DEBUG"; break;
    case CE_LOG_WARNING: log_level_name = "WARNING"; break;
    case CE_LOG_ERROR: log_level_name = "ERROR"; break;
    case CE_LOG_FATAL: log_level_name = "FATAL ERROR"; break;
    default:
        assert(!"Unknown logging level"); break;
    }

	AST::NodeT<AST::List> log_front, *log;
	log = Config::InsertEntry(&log_front, "name", m_LogFilename);
	log = Config::InsertEntry(log, "level", log_level_name);

	root = Config::InsertEntry(root, "log", std::move(log_front));

	size_t res_cnt = m_Resources.size();
	if(res_cnt)
	{
		AST::NodeT<AST::List> resources_root, *resources;
        resources = Config::InsertEntry(&resources_root, "name", m_Resources[0]);
		for(size_t i = 1; i < res_cnt; ++i)
			resources = Config::InsertEntry(resources, "name", m_Resources[i]);
		root = Config::InsertEntry(root, "resources", std::move(resources_root));
	}
	
	size_t ctx_size = m_Contexts.size();
    if(ctx_size)
    {
		AST::NodeT<AST::List> contexts_root, *contexts;
        contexts = m_Contexts[0]._save(&contexts_root);
        for(size_t i = 1; i < ctx_size; ++i)
            contexts = m_Contexts[i]._save(contexts);
		root = Config::InsertEntry(root, "contexts", std::move(contexts_root));
    }


    Carina::Path p(filename);
    Carina::Path dir(p.directoryPath());
    if(!dir.isValid())
    {
        Carina::FSOpResult res = Carina::Directory::mkdir(dir.get());
        if(res == CE_FS_ERROR)
            return false;
    }
    
	std::fstream fs(filename.c_str(), std::ios::out);
	if(!fs.is_open())
		return false;
    Config::Printer printer(fs, 0);
	root_front.accept(&printer);
	return fs.good();
}

void SettingsFile::clear()
{
    m_Resources.clear();
    m_RendererName.clear();
}

void SettingsFile::setRenderer(const string& str)
{
    m_RendererName = str;
}

string SettingsFile::getRenderer() const
{
    return m_RendererName;
}

void SettingsFile::addResource(const string& str)
{
    m_Resources.push_back(str);
}

void SettingsFile::removeResource(const string& str)
{
    auto iter = std::find(m_Resources.begin(), m_Resources.end(), str);
    m_Resources.erase(iter);
}

string SettingsFile::getResource(size_t idx) const
{
    return m_Resources[idx];
}

size_t SettingsFile::getResourceCount() const
{
    return m_Resources.size();
}

string SettingsFile::getDefaultConfig()
{
    string homedir;
    if(!System::GetEnvironmentVariable(HOME_DIR, homedir))
        return "";
    return homedir + CONFIG_FILE;
}

string SettingsFile::getLogFile() const
{
    return m_LogFilename == "${LOG_FILE}" ? getDefaultLogFile() : m_LogFilename;
}

void SettingsFile::setLogFile(const string& filename)
{
    m_LogFilename = filename;
}

string SettingsFile::getDefaultLogFile()
{
    string homedir;
    if(!System::GetEnvironmentVariable(HOME_DIR, homedir))
        return "";
    return homedir + LOG_FILE;
}

void SettingsFile::setLogLevel(LoggingLevel log_level)
{
    m_LoggingLevel = log_level;
}

LoggingLevel SettingsFile::getLogLevel() const
{
    return m_LoggingLevel;
}

void SettingsFile::setDefaultLogFile()
{
    m_LogFilename = "${LOG_FILE}";
}

bool SettingsFile::isDefaultLogFile() const
{
    return m_LogFilename == "${LOG_FILE}";
}

void SettingsFile::addContext(const string& name)
{
    m_Contexts.push_back(ContextSettings(name));
}

void SettingsFile::deleteContext(const string& name)
{
    auto iter = std::find_if(m_Contexts.begin(), m_Contexts.end(), [&name](const ContextSettings& ctx) { return ctx.getName() == name; });
    m_Contexts.erase(iter);
}

ContextSettings& SettingsFile::getContext(const string& name)
{
    return *std::find_if(m_Contexts.begin(), m_Contexts.end(), [&name](const ContextSettings& ctx) { return ctx.getName() == name; });
}

ContextSettings& SettingsFile::lastContext()
{
    return m_Contexts.back();
}

ContextSettings& SettingsFile::getContext(size_t ctx_idx)
{
    return m_Contexts[ctx_idx];
}

size_t SettingsFile::getContextCount()
{
    return m_Contexts.size();
}

ContextSettings::ContextSettings() {}

ContextSettings::ContextSettings(const string& name)
    :   m_Name(name) {}

void ContextSettings::_load(const AST::List* ctx)
{
    auto width_node = Config::FindChild(ctx, "width");
    auto height_node = Config::FindChild(ctx, "height");
    auto name_node = Config::FindChild(ctx, "name");
	auto msaa_node = Config::FindChild(ctx, "MSAA");
	m_Width = Config::ExtractValue<int>(width_node);
	m_Height = Config::ExtractValue<int>(height_node);
    m_Name = Config::ExtractValue<string>(name_node);
	m_MSAA = Config::ExtractValue<int>(msaa_node);
}

AST::NodeT<AST::List>* ContextSettings::_save(AST::NodeT<AST::List>* ctx) const
{
	AST::NodeT<AST::List> ctx_params_root, *ctx_params;
    ctx_params = Config::InsertEntry(&ctx_params_root, "width", m_Width);
	ctx_params = Config::InsertEntry(ctx_params, "height", m_Height);
	ctx_params = Config::InsertEntry(ctx_params, "name", m_Name);
	ctx_params = Config::InsertEntry(ctx_params, "MSAA", m_MSAA);
	return Config::InsertEntry(ctx, "context", std::move(ctx_params_root));
}

void ContextSettings::setWidth(size_t width)
{
    m_Width = width;
}

size_t ContextSettings::getWidth() const
{
    return m_Width;
}

void ContextSettings::setHeight(size_t height)
{
    m_Height = height;
}

size_t ContextSettings::getHeight() const
{
    return m_Height;
}

void ContextSettings::setName(const string& name)
{
    m_Name = name;
}

string ContextSettings::getName() const
{
    return m_Name;
}

void ContextSettings::setMSAA(size_t samples)
{
    m_MSAA = samples;
}

size_t ContextSettings::getMSAA() const
{
    return m_MSAA;
}
}
